#!/bin/sh

# provide the shortest possible unique hardware path to a device
# for the Linux Persistent Device Naming scheme
#
# Copyright (C) 2005-2006 SUSE Linux Products GmbH
# Author:
#	Hannes Reinecke <hare@suse.de>
#
#	This program is free software; you can redistribute it and/or modify it
#	under the terms of the GNU General Public License as published by the
#	Free Software Foundation version 2 of the License.
#
# to be called from a udev rule to return the name for a symlink
#	DEVPATH=<devpath>; path_id
#	path_id <devpath>

# examples for all block devices on a system:
#	for i in `find /sys/class/block`; do DEVPATH="`echo $i | sed -e 's@^/sys\|/dev@@g'`"; path_id; done

SYSFS=/sys
RESULT=1
TYPE=
OPWD="`pwd`"
full_sysfs_path=
full_sysfs_device_path=

if [ -z "$DEVPATH" -a -z "$1" ] ; then
	exit 1
fi

if [ -z "$DEVPATH" ] ; then
	case "$1" in
		$SYSFS/*)
			DEVPATH="${1#$SYSFS}"
			;;
		*)
			DEVPATH=$1
			;;
	esac
fi

if [ ! -e $SYSFS$DEVPATH/dev ] ; then
	exit 1
fi

case "$DEVPATH" in
	/devices/*)
		cd "$SYSFS$DEVPATH/subsystem";
		TYPE="`pwd -P`"
		cd "$OPWD"
		TYPE="${TYPE##*/}"
		;;
	/class/*)
		TYPE="${DEVPATH#/class/}"
		TYPE="${TYPE%%/*}"
		;;
	/block/*)
		TYPE=block
		;;
	*)
		exit 1
		;;
esac

get_port_offset () {
	local type offset port
	type=$1
	offset=$2
	for i in $type[0-9]* ; do
		: i $i
		port="${i#$type}"
		if [ "$port" -lt "$offset" ] ; then offset=$port ; fi
	done
	echo $offset
}

handle_pci () {
	local DEV=$1
	cd -P $1
	DEV=${PWD}
	pci_id=${DEV##*/}
	host_dev_path=$DEV
	while [ ! -z "$host_dev_path" ] ; do
		case "$host_dev_path" in
			*/pci[0-9]*)
				host_dev_path=${host_dev_path%/*}
				;;
			*)
				break
				;;
		esac
	done
	d="pci-$pci_id-$d"
	D="$host_dev_path"
	RESULT=0
}

handle_platform () {
	local DEV=$1
	cd -P $1
	DEV=${PWD}
	platform_id=${DEV##*/}
	host_dev_path=$DEV
	while [ ! -z "$host_dev_path" ] ; do
		case "$host_dev_path" in
			*/platform*)
				host_dev_path=${host_dev_path%/*}
				;;
			*)
				break
				;;
		esac
	done
	if [ "$d" ]; then
		d="platform-$platform_id-$d"
	else
		d="platform-$platform_id"
	fi
	D="$host_dev_path"
	RESULT=0
}

handle_serio () {
	local DEV=$1
	cd -P $1
	DEV=${PWD}
	serio_id=${DEV##*/serio}
	host_dev_path=$DEV
	while [ ! -z "$host_dev_path" ] ; do
		case "$host_dev_path" in
			*/serio*)
				host_dev_path=${host_dev_path%/*}
				;;
			*)
				break
				;;
		esac
	done
	if [ "$d" ]; then
		d="serio-$serio_id-$d"
	else
		d="serio-$serio_id"
	fi
	D="$host_dev_path"
	RESULT=0
}

handle_ide () {
	: handle_ide $*
	local DEV=$1
	local port idedev idecontroller
	# IDE
	: DEV $DEV
	port=${DEV##*/}
	idedev=${DEV%/*}
	idecontroller=${idedev%/*}
	# port info if the controller has more than one interface
	port="${port#ide}"
	: port $port d $d
	: idedev $idedev kernel_port $port
	case "${port#*.}" in
		0)
			channel=0
			;;
		1)
			channel=1
			;;
		*)
			echo "Error: $idedev is neither master or slave" >&2
			;;
	esac
	cd $idecontroller
	offset="`get_port_offset ide ${port%.*}`"
	cd "$OPWD"
	:  port offset $offset
	port=$((${port%.*} - $offset))
	if [ "$d" ] ; then
		d="ide-${port}:$channel-$d"
	else
		d="ide-${port}:$channel"
	fi
	D=$idecontroller
	RESULT=0
}

handle_scsi () {
	: handle_scsi $*
	local DEV=$1
	local cil controller_port controller_dev
	# SCSI device
	cil="${DEV##*/}"
	cil="${cil#*:}"
	target_dev=${DEV%/*}
	target_id=${target_dev##*/target}
	cd "$target_dev"
	target_num=0
	for tid in ${target_id}* ; do
		target_num=$(( $target_num + 1 ))
	done
	controller_port=${target_dev%/*}
	controller_dev="${controller_port%/*}"
	: controller_dev $controller_dev
	: controller_port $controller_port
	# a host controller may have more than one interface/port
	controller_port="${controller_port##*/host}"
	#
	cd "$controller_dev"
	controller_offset=$(get_port_offset host $controller_port)
	cd "$OPWD"
	controller_port=$(( $controller_port - $controller_offset))
	scsi_id="scsi-${controller_port}:${cil}"
	if [ "$d" ] ; then
		d="${scsi_id}-$d"
	else
		d="$scsi_id"
	fi
	D="$controller_dev"
	RESULT=0
}

handle_firewire () {
	: handle_firewire $*
	local DEV=$1
	if [ -f "$D/ieee1394_id" ] ; then
		read ieee1394_id < $D/ieee1394_id
	fi
	if [ -z "$ieee1394_id" ] ; then
		: no IEEE1394 ID
		RESULT=1
		return
	fi
	fw_host_dev=${DEV%/fw-host*}
	# IEEE1394 devices are always endpoints
	d="ieee1394-0x$ieee1394_id"
	D="$fw_host_dev"
	RESULT=0
}

handle_fc () {
	: handle_fc $*
	local DEV=$1
	local cil controller_port controller_dev
	# SCSI-FC device
	fc_tgt_hcil="${DEV##*/}"
	fc_tgt_lun="${fc_tgt_hcil##*:}"
	fc_tgt_path="${DEV%/*}"
	fc_tgt_num="${fc_tgt_path##*/}"
	fc_tgt_dev="${fc_tgt_path}/fc_transport:${fc_tgt_num}"
	if [ -e "$fc_tgt_dev/port_name" ]; then
		read wwpn < $fc_tgt_dev/port_name
	fi
	if [ -z "$wwpn" ] ; then
		: no WWPN
		D=
		RESULT=1
		return
	fi
	# Linux currently knows about 32bit luns
	tmp_lun3=$(printf "%04x" $(($fc_tgt_lun & 0xFFFF)))
	tmp_lun2=$(printf "%04x" $(( ($fc_tgt_lun >> 16) & 0xFFFF)))
	tmp_lun1="0000"
	tmp_lun0="0000"
	if (($fc_tgt_lun == 0)) ; then
		lun="0x0000000000000000"
	else
		lun="0x${tmp_lun3}${tmp_lun2}${tmp_lun1}${tmp_lun0}"
	fi
	controller_dev="${fc_tgt_path%/host[0-9]*}"
	# FC devices are always endpoints
	d="fc-${wwpn}:${lun}"
	D="$controller_dev"
	RESULT=0
}

handle_sas () {
	: handle_sas $*
	local DEV=$1
	local cil adapter controller_dev
	# SAS device
	sas_host_path="${DEV%%/port*}"
	sas_phy_path="${DEV#*/host*/}"
	sas_phy_path="${sas_phy_path%%/target*}"
	sas_phy_id="${sas_phy_path%%/*}"
	sas_phy_id="${sas_phy_id##*port-}"
	sas_port_id="${sas_phy_path%%/end_device*}"
	sas_port_id="${sas_port_id##*port-}"
	sas_end_id="${sas_phy_path##*end_device-}"
	sas_phy_dev="/sys/class/sas_phy/phy-${sas_phy_id}"
	if [ -e "$sas_phy_dev/sas_address" ]; then
		read phy_address < $sas_phy_dev/sas_address
		read phy_id < $sas_phy_dev/phy_identifier
	fi
	if [ -z "$phy_address" ] ; then
		: no initiator address
		D=
		RESULT=1
		return
	fi
	sas_port_dev="/sys/class/sas_port/port-${sas_port_id}"
	if [ -e "$sas_port_dev/num_phys" ] ; then
		read phy_port < $sas_port_dev/num_phys
	fi
	if [ -z "$phy_port" ] ; then
		: no initiator address
		D=
		RESULT=1
		return
	fi
	sas_phy_address="$phy_address:$phy_port:$phy_id"
	sas_end_dev="/sys/class/sas_device/end_device-${sas_end_id}"
	if [ -e "$sas_end_dev/sas_address" ]; then
		read end_address < $sas_end_dev/sas_address
		read end_id < $sas_end_dev/phy_identifier
	fi
	if [ -z "$end_address" ] ; then
		: no initiator address
		D=
		RESULT=1
		return
	fi
	sas_end_address="$end_address:$end_id"
	controller_dev="${sas_host_path%/host[0-9]*}"
	# SAS devices are always endpoints
	d="sas-${sas_phy_address}-${sas_end_address}"
	D="$controller_dev"
	RESULT=0
}

handle_iscsi() {
	local DEV=$1
	local iscsi_session_dir
	local iscsi_session iscsi_session_path
	local iscsi_connection iscsi_connection_path
	# iSCSI device
	iscsi_session_dir="${DEV%%/target*}"
	iscsi_session="${iscsi_session_dir##*/}"
	iscsi_session_path=/sys/class/iscsi_session/${iscsi_session}
	if [ ! -d "$iscsi_session_path" ] ; then
	    : no iSCSI session path
	    RESULT=1
	    return
	fi
	# Currently we're not doing MC/S
	for conn in ${iscsi_session_dir}/connection* ; do
	    iscsi_conn_num=${conn##*:}
	    if [ "$iscsi_conn_num" = '0' ] ; then
		iscsi_connection=$(basename $conn)
	    fi
	done
	if [ -z "$iscsi_connection" ] ; then
	    : no iSCSI connection found
	    RESULT=1
	    return
	fi
	iscsi_connection_path=/sys/class/iscsi_connection/${iscsi_connection}
	if [ ! -d "$iscsi_connection_path" ] ; then
	    : no iSCSI connection path
	    RESULT=1
	    return
	fi
	if [ -e "${iscsi_session_path}/targetname" ]; then
	    read iscsi_tgtname < ${iscsi_session_path}/targetname
	fi
	if [ -z "$iscsi_tgtname" ] ; then
	    : No iSCSI Targetname
	    RESULT=1
	    return
	fi
	if [ -e "${iscsi_connection_path}/persistent_address" ] ; then
	    read iscsi_address < ${iscsi_connection_path}/persistent_address
	fi
	if [ -z "$iscsi_address" ] ; then
	    : No iSCSI Target address
	    RESULT=1
	    return
	fi
	if [ -e "${iscsi_connection_path}/persistent_port" ] ; then
	    read iscsi_port < ${iscsi_connection_path}/persistent_port
	fi
	d="ip-${iscsi_address}:${iscsi_port}-iscsi-${iscsi_tgtname}"
	RESULT=0
}

handle_usb () {
: handle_usb $*
	local DEV=$1
	cd -P $1
	DEV=${PWD}
	port_id=${DEV##*/}
	port_num=${port_id#*-}
	host_dev_path=$DEV
	while [ ! -z "$host_dev_path" ] ; do
		case "$host_dev_path" in
			*/usb*)
				usb_host_path=$host_dev_path
				host_dev_path="${host_dev_path%/*}"
				;;
			*)
				break
				;;
		esac
	done
	: host_dev_path $host_dev_path
	usb_host_num=${usb_host_path##*/usb}

	cd "$host_dev_path"
	usb_host_offset=$(get_port_offset usb $usb_host_num)
	usb_host_port=$(($usb_host_num - $usb_host_offset))
	cd "$OPWD"
	if [ "$d" ] ; then
		d="usb-$usb_host_port:$port_num-${d}"
	else
		d="usb-$usb_host_port:$port_num"
	fi
	D="$host_dev_path"
	RESULT=0
}

handle_device () {
	full_sysfs_path="$SYSFS$DEVPATH"
	case "$DEVPATH" in
		/devices/*)
			# new sysfs layout
			if [ -L $full_sysfs_path/subsystem ]; then
				full_sysfs_path="${full_sysfs_path%/*}"
				cd "$full_sysfs_path/subsystem";
				subsys="`pwd -P`"
				cd "$OPWD"
				subsys="${subsys##*/}"
				if [ "$subsys" = "block" ]; then
					# parent is "block", it's a partition, move one up
				full_sysfs_path="${full_sysfs_path%/*}"
				fi
				cd $full_sysfs_path
			fi
			;;
		*)
			# old sysfs layout
			if [ ! -L $full_sysfs_path/device ] ; then
				if [ -f $full_sysfs_path/range ] ; then return ; fi
				full_sysfs_path="${full_sysfs_path%/*}"
				: full_sysfs_path "$full_sysfs_path"
				if [ ! -L $full_sysfs_path/device -o ! -f $full_sysfs_path/dev ] ; then
					return
				fi
			fi
			cd $full_sysfs_path/device
			;;
	esac
	full_sysfs_device_path="`pwd -P`"
	cd "$OPWD"
	D=$full_sysfs_device_path
	while [ ! -z "$D" ] ; do
		case "$D" in
			*/ide[0-9]/[0-9].[0-9]*|*/ide[0-9][0-9]/[0-9][0-9].[0-9]*)
				handle_ide "$D"
				;;
			*/css0/*)
				if [ -r $full_sysfs_device_path/wwpn ]; then
					read wwpn < $full_sysfs_device_path/wwpn
				fi
				if [ -r $full_sysfs_device_path/fcp_lun ]; then
					read lun < $full_sysfs_device_path/fcp_lun
				fi
				if [ -r $full_sysfs_device_path/hba_id ]; then
					read bus_id < $full_sysfs_device_path/hba_id
				fi
				if [ "$bus_id" -a "$wwpn" -a "$lun" ]; then
					# S/390 zfcp adapter
					d="ccw-$bus_id-zfcp-$wwpn:$lun"
					RESULT=0
				else
					# DASD devices
					bus="ccw"
					adapter=${D##*/}
					d="$bus-$adapter"
					RESULT=0
				fi
				D=
				;;
			*/rport-[0-9]*:[0-9]*-[0-9]*/*)
				handle_fc "$D"
				;;
			*/end_device-[0-9]*:[0-9]*:[0-9]*/*)
				handle_sas "$D"
				;;
			*/fw-host[0-9]*/*)
				handle_firewire "$D"
				;;
			*/session[0-9]*/*)
				handle_iscsi "$D"
				D=
				;;
			*/host[0-9]*/[0-9]*:[0-9]*:[0-9]*:[0-9]*)
				handle_scsi "$D"
				;;
			*/usb[0-9]*/[0-9]*/*)
				handle_usb "$D"
				;;
			*/pci[0-9]*:[0-9]*)
				handle_pci "$D"
				;;
			*/serio[0-9]*)
				handle_serio "$D"
				;;
			*/platform/*)
				handle_platform "$D"
				;;
			*/devices)
				D=
				;;
			*)
				: not handled
				RESULT=1
				return
				;;
		esac
	done
	if [ "$TYPE" = "scsi_tape" ] ; then
		devname=${full_sysfs_path##*/}
		rewind="${devname%%st*}"
		mode="${devname##*st}"
		case "$mode" in
			*l)
				mode="l"
				;;
			*m)
				mode="m"
				;;
			*a)
				mode="a"
				;;
			*)
				mode=""
			   ;;
		esac
		if [ "$d" ]; then
			d="$d-${rewind}st${mode}"
		fi
	fi
}

case "$TYPE" in
	block)
		handle_device
		echo "ID_PATH=$d"
		;;
	scsi_tape)
		handle_device
		echo "ID_PATH=$d"
		;;
	input)
		handle_device
		echo "ID_PATH=$d"
		;;
	*)
		RESULT=1
		;;
esac

exit $RESULT
